/**
 * 
 */
package jpb.hotspot;

import jpb.*;
import jpb.command.CompositeSelector;
import jpb.command.JavaHomeSelector;
import jpb.command.OsPathSelector;
import jpb.command.ValidatingSelector;
import jpb.process.ProcessExecutor;
import jpb.process.ProcessResult;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * Represents specifics of the Sun's Hotspot JVM.
 * 
 * @author Dariusz Kordonski
 */
public class HotspotJvm implements Jvm
{
    public static final class CustomParams {
        private CustomParams() {
            throw new AssertionError("Don't instantiate me");
        }

        /**
         * Custom Hotspot VM argument specifying maximum permanent generation size within the VM heap.
         *
         */
        public static final JvmArg<MemArg> MAX_PERM_SIZE = new MaxPermSize();
    }

    static enum CustomParamNames {
        MAX_PERM_SIZE
    }

    static enum Config
    {
        DRY_RUN_WAIT("jpb.hotspot.dryrun.wait", "1");

        private final String key;
        private final String defaultValue;

        Config(String key, String defaultValue) {
            this.key = key;
            this.defaultValue = defaultValue;
        }

        String get()
        {
            return System.getProperty(key, defaultValue);
        }
    }

    private final EntryPointArg entryPointArg = new EntryPointArg();
    private final ClassPathArg classPathArg = new ClassPathArg();
    private final MaxMemoryArg maxMemoryArg = new MaxMemoryArg();
    private final MinMemoryArg minMemoryArg = new MinMemoryArg();
    private final MaxStackSizeArg maxStackSizeArg = new MaxStackSizeArg();
    private final SystemPropertiesArg systemPropertiesArg = new SystemPropertiesArg();
    private final StartupCommandSelector selector = createSelector();

    private final ProcessExecutor processExecutor = new ProcessExecutor();

    private StartupCommandSelector createSelector() {
        return new CompositeSelector(
                new ValidatingSelector(new OsPathSelector(), this),
                new JavaHomeSelector()
        );
    }

    /**
     * The default command selector for Hotspot performs the following steps to determine the appropriate 'Java' command:
     * <ul>
     * <li>runs 'java -version' to check if the java command is on the operating system path. If so, bare 'java' command
     * is returned, which means that the java command on the OS path will be used to execute Java processes
     * <li>checks if JAVA_HOME environment variable is set in the current environment. If so, a following path to the
     * java command is returned: '&lt;JAVA_HOME&gt;/bin/java', where &lt;JAVA_HOME&gt; is the value of the JAVA_HOME
     * environment variable
     * <li>if neither of the above is successful, an empty command is returned to indicate unsuccessful java command
     * resolution
     * </ul>
     * 
     * @return {@inheritDoc}
     */
    @Override
    public StartupCommandSelector commandSelector() {
        return selector;
    }

    @Override
    public boolean dryRun(CommandArg startupCommand) {
        final ProcessBuilder dryRunBuilder = new ProcessBuilder().redirectErrorStream(true);
        startupCommand.addTo(dryRunBuilder.command());
        dryRunBuilder.command().add("-version");
        return checkResult(dryRunBuilder);
    }

    private boolean checkResult(ProcessBuilder dryRunBuilder) {
        Process process;
        try {
            process = dryRunBuilder.start();
            final ProcessResult result = processExecutor.execute(process).get(waitTime(), TimeUnit.SECONDS);
            // TODO log output / check output to verify?
            return result.returnCode == 0;
        } catch (IOException e) {
            return false;
        } catch (InterruptedException e) {
            // TODO restore interrupted state?
            return false;
        } catch (ExecutionException e) {
            return false;
        } catch (TimeoutException e) {
            return false;
        }
    }

    private long waitTime() {
        return Long.parseLong(Config.DRY_RUN_WAIT.get());
    }

    @Override
    public JvmArg<EntryPoint> entryPointArg() {
        return entryPointArg;
    }

    @Override
    public JvmArg<List<File>> classPathArg() {
        return classPathArg;
    }

    @Override
    public JvmArg<MemArg> maxMemoryArg() {
        return maxMemoryArg;
    }

    @Override
    public JvmArg<MemArg> minMemoryArg() {
        return minMemoryArg;
    }

    @Override
    public JvmArg<MemArg> maxStackSizeArg() {
        return maxStackSizeArg;
    }

    @Override
    public JvmArg<Map<String, String>> systemPropertiesArg() {
        return systemPropertiesArg;
    }

}
